home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
MacWorld 1997 September
/
Macworld (1997-09).dmg
/
Serious Software
/
Cherwell Scientific Demos
/
pro Fit
/
pro Fit 5.0 demo (fpu).sea
/
pro Fit 5.0 demo (fpu)
/
External Modules
/
External modules sources
/
Pascal
/
GuideLine.p
< prev
next >
Wrap
Text File
|
1996-04-26
|
24KB
|
743 lines
{******************************************************************************}
{ GuideLine.p }
{ }
{ }
{ Version 22.04.96 }
{******************************************************************************}
unit GuideLine;
interface
{$IFC UNDEFINED THINK_PASCAL }
uses
Types, Memory, proFit_interface;
{$ELSEC}
uses
proFit_interface;
{$ENDC}
procedure main (selector: integer; pb: ExtModulesParamBlockPtr);
implementation
{$IFC NOT UNDEFINED OLDROUTINENAMES }
{$IFC NOT OLDROUTINENAMES }
procedure DisposPtr (p: Ptr); { if we don't have defined DisposPtr }
begin
DisposePtr(p);
end;
{$ENDC}
{$ENDC}
{ note: MPW users must make sure that the procedure main is at the beginning of the compiled code }
{ under Think Pascal, this is cared for by the compiler }
{ We let main call a function mainMain to make sure that the code starts with a jump to }
{ our entry point even when compiling under MPW Pascal }
procedure mainMain (selector: integer; pb: ExtModulesParamBlockPtr);
forward;
procedure main (selector: integer; pb: ExtModulesParamBlockPtr);
begin
mainMain(selector, pb);
end;
const
maxGuidePts = 100; { maximum number of points to define the smooth curve }
type
MyGlobals = record
nrGuidePts: integer; { effective number of points defining the smooth curve }
xv, yv, mv: array[1..maxGuidePts] of extended;{ points and slope of this curve }
end;
ptrToMyGlobals = ^MyGlobals;
ValsT = array[1..8192] of real;
ValsP = ^ValsT;
function CubicCurve (xm, x1, y1, m1, x2, y2, m2: extended): extended;
{ 2 points with their slope given define a cubic polygon; the y value at xm is calculated. }
var
aa, bb, cc, dd: extended;
dx, dy, x1x1: extended;
begin
dx := x1 - x2;
dy := y1 - y2;
aa := (dx * (m1 + m2) - 2 * dy) / (dx * dx * dx);
x1x1 := x1 * x1;
bb := ((m1 - m2) - 3 * aa * (x1x1 - x2 * x2)) / 2 / dx;
cc := m1 - 3 * aa * x1x1 - 2 * bb * x1;
dd := y1 - aa * x1x1 * x1 - bb * x1x1 - cc * x1;
CubicCurve := aa * xm * xm * xm + bb * xm * xm + cc * xm + dd;
end; { CubicCurve }
procedure FindAveragedVal (nrPoints: integer; xvals, yvals: ValsP; log: boolean; {}
AA, reg, eps, xxx: extended; var yyy: extended);
{ this function calculates an averaged value at x=xxx, by summing over all points given, }
{ and weigthing them linearly or logarithmically and using either linear or quadratic regression. }
var
s1, s2, s3, s4, s5, s6, s7, s8: extended; { summing parameters }
xi, yi, gi: extended; { coordinates and weight of point i }
m, b: extended; { parameters for linear regression }
va, vb, vc: extended; { parameters for quadratic regression }
val1, val2: extended; { found for averaging }
ex, ex1: extended;
i, ccount: integer;
begin
s1 := 0; { sum gi (weights of points xi) }
s2 := 0; { sum gi*xi }
s3 := 0; { sum gi*yi }
s4 := 0; { sum gi*xi*yi }
s5 := 0; { sum gi*xi*xi }
s6 := 0; { sum gi*xi*xi*xi }
s7 := 0; { sum gi*xi*xi*xi*xi }
s8 := 0; { sum gi*xi*xi*yi }
ccount := 0;
for i := 1 to nrPoints do
begin
if TestStop then
exit(FindAveragedVal);
xi := xvals^[i];
if log then { different weighting factors for log and lin cases }
gi := exp(-sqr(AA * ln(xi / xxx)))
else
gi := exp(-sqr(AA * (xi - xxx)));
if gi > 1e-9 then
begin
yi := yvals^[i];
ccount := ccount + 1;
if ccount <= 3 then { if two points have the same x-value ... }
if ccount = 1 then
val1 := xi
else if ccount = 2 then
if abs(xi - val1) < eps then
ccount := 1
else
val2 := xi
else { if ccount = 3 then }
if (abs(xi - val1) < eps) | (abs(xi - val2) < eps) then
ccount := 2;
s1 := gi + s1; { summing }
s2 := gi * xi + s2;
s3 := gi * yi + s3;
s4 := gi * xi * yi + s4;
ex := gi * xi * xi;
s5 := ex + s5;
if reg = 2 then
begin
s6 := ex * xi + s6;
s7 := ex * xi * xi + s7;
s8 := ex * yi + s8;
end;
end;
end; { for i := 1 to nrPoints do }
{ final evaluation: }
if ccount = 0 then
else if ccount = 1 then { no regression }
yyy := s3 / s1
else
begin
ex := s5 * s1 - s2 * s2;
if (reg = 1) | (ccount = 2) then { linear regression }
begin
m := (s4 * s1 - s2 * s3) / ex;
b := (s3 - m * s2) / s1;
yyy := m * xxx + b;
end
else
begin { quadratic regression }
ex1 := s5 * s2 - s6 * s1;
va := (ex * (s8 * s1 - s3 * s5) - ex1 * (s3 * s2 - s4 * s1)) / (ex * (s7 * s1 - s5 * s5) - sqr(ex1));
vb := (va * ex1 + (s4 * s1 - s3 * s2)) / ex;
vc := (-va * s5 - vb * s2 + s3) / s1;
yyy := va * xxx * xxx + vb * xxx + vc;
end;
end; { if count >1 then }
end; { FindAveragedVal }
{***********************************************************************************************}
procedure SetUp (var moduleKind: integer; { set moduleKind to isFunction or isProgram }
var name: Str255; { the name of the program or function }
var requiredGlobals: longint; { the number of bytes to be allocated in ExtModulesParamBlock.globals }
{ set requiredGlobals to 0 if you don't use this feature }
pb: ExtModulesParamBlockPtr); { the complete parameter block passed by pro Fit to the }
{ routines defined in this file. In most cases it can be ignored }
{ SetUp is called once when the external module is linked to pro Fit }
begin
moduleKind := isFunction;
name := 'GuideLine';
requiredGlobals := sizeof(MyGlobals);
end;
{***********************************************************************************************}
procedure InitializeFunc (var hasDerivatives: boolean; { set this to true if and only if you define the function }
{ Derivatives to calculate the partial derivatives of the parameters }
var descr1stLine, descr2ndLine: Str255; { The two lines of the text in the parameter window }
var numberOfParams: integer; { the number of parameters of the function }
var a0: DefaultParamInfo; { the default names, values etc. of the parameters }
pb: ExtModulesParamBlockPtr); { the complete parameter block passed by pro Fit to the }
{ routines defined in this file. In most cases it can be ignored }
{ InitializeFunc is called once (after SetUp has been called) when the external module is linked to pro Fit }
{ Used to set all the information needed to describe a function }
begin
hasDerivatives := false;
descr1stLine := 'smoothness: 0-100 %; interval: 0=auto, 1=linear, 2=logarithmic, 3=point density; ';
descr2ndLine := '$BSelect data$averaging: 0=mean, 1=linear regr., 2=quadratic regr.';
{ Using $B...$ in descr2ndLine creates a button in the parameter window, }
{ which, when pressing, calls the function Check() with 30000 as argument. }
numberOfParams := 3;
a0.value^[1] := 80;
a0.mode^[1] := constant;
a0.lowest^[1] := 0;
a0.highest^[1] := 100;
a0.name^[1] := 'smooth';
a0.value^[2] := 0;
a0.mode^[2] := constant;
a0.lowest^[2] := 0;
a0.highest^[2] := 3;
a0.name^[2] := 'interval';
a0.value^[3] := 0;
a0.mode^[3] := constant;
a0.lowest^[3] := 0;
a0.highest^[3] := 2;
a0.name^[3] := 'average';
pb^.v[1] := 0.31938153;
pb^.v[2] := -0.356563782;
pb^.v[3] := 1.781477937;
pb^.v[4] := -1.821255978;
pb^.v[5] := 1.330274429;
pb^.v[6] := 0; { the x-column to be used, 0 if not defined yet. }
pb^.v[7] := 0; { the y-column to be used, 0 if not defined yet. }
pb^.v[8] := 0; { the window ID of the selected data window, 0 if not defined yet. }
end;
{***********************************************************************************************}
function Check (ParamNo: integer; { the parameter that was changed }
var a0: DefaultParamInfo; { the default names, values etc. of the parameters }
pb: ExtModulesParamBlockPtr { the complete parameter block passed by pro Fit to the}
{ routines defined in this file. In most cases it can be ignored }
): CheckPAnswer;
{ Can be left emtpy (returning good) if not needed. }
{ called when the user has changed a value in the parameters window. This routine }
{ can then check if this parameters is fine. It can also change some of the }
{ other entries in a0. The returned values can be: }
{ good: return this value if you agree with the new parameter value }
{ update: return this value if you want the parameters window }
{ to be updated because you changed some of the values in a0 }
{ bad: return this value if you want the new parameter value to be refused }
var
inprec: inputRec; { variables to call InputBox() }
inpID, inpXcol, inpYcol, pHelp: extended; { data window id and column number of x- and y-values }
inpIDStr, inpXcolStr, inpYcolStr, pHelpStr: Str255; { texts for inputBox }
begin
Check := good;
if (ParamNo = 2) & (a0.value^[2] <> 0) & (a0.value^[2] <> 1) & (a0.value^[2] <> 2) & (a0.value^[2] <> 3) then
Check := bad;
if (ParamNo = 3) & (a0.value^[3] <> 0) & (a0.value^[3] <> 1) & (a0.value^[3] <> 2) then
Check := bad;
if (ParamNo = 30000) then
begin
if (pb^.v[6] = 0) then
inpXcol := xColumn { generates an error if there is no data window }
else
inpXcol := pb^.v[6];
if (pb^.v[7] = 0) then
inpYcol := yColumn
else
inpYcol := pb^.v[7];
if (pb^.v[8] = 0) then
inpID := GetCurrentWindow(dataType)
else
inpID := pb^.v[8];
pHelp := 0;
inpIDStr := '$WData window :';
inpXcolStr := '$CX column :';
inpYcolStr := '$CY column :';
pHelpStr := '$XPrint help into Results window';
inprec[1].x := @inpID;
inprec[1].s := @inpIDStr;
inprec[2].x := @inpXcol;
inprec[2].s := @inpXcolStr;
inprec[3].x := @inpYcol;
inprec[3].s := @inpYcolStr;
inprec[4].x := @pHelp;
inprec[4].s := @pHelpStr;
SetBoxTitle('GuideLine Setup');
if InputBox(4, inprec) then
begin
StopExecution;
exit(Check);
end;
pb^.v[6] := inpXcol;
pb^.v[7] := inpYcol;
pb^.v[8] := inpID;
if pHelp > 0 then
begin
WritelnString('');
WritelnString('');
WritelnString('Short description of the function GuideLine:');
WritelnString('-------------------------------------------');
WritelnString('GuideLine generates a curve going smoothly through a given data set.');
WritelnString('');
WritelnString('The curve is found in the following way:');
WritelnString('a) The given data set is divided into subsets of data points.');
WritelnString('b) From each subset a "synthetic data point" is generated.');
WritelnString(' The synthetic data point is the average of all points in');
WritelnString(' the subset.');
WritelnString('c) GuideLine returns a cubic spline curve going through');
WritelnString(' the synthetic data points.');
WritelnString('');
WritelnString('The number of subsets is adjusted by the parameter smooth:');
WritelnString(' - a[1] = 0 : there will be 100 subsets.');
WritelnString(' - a[1] = 100: there will 1 subset only.');
WritelnString('The algorithm for selecting the subsets (intervals) is');
WritelnString('defined by a[2]:');
WritelnString(' - a[2] = 0: automatic.');
WritelnString(' - a[2] = 1: linear (subsets have same width on linear scale).');
WritelnString(' - a[2] = 2: logarithmic (subsets have same width on log. scale).');
WritelnString(' - a[2] = 3: point density (subsets have same number of data).');
WritelnString('The averaging method is defined by a[3]: ');
WritelnString(' - a[3] = 0: mean values of x- and y-coordinates.');
WritelnString(' - a[3] = 1: y = linear regression at the mean x-coordinate.');
WritelnString(' - a[3] = 2: y = quadratic regression at the mean x-coordinate.');
WritelnString('');
end;
Check := good;
end;
end; { Check }
{***********************************************************************************************}
procedure First (a: ParamArray; { the new parameters }
pb: ExtModulesParamBlockPtr); { the complete parameter block passed by pro Fit to the}
{ routines defined in this file. In most cases it can be ignored }
{ Can be left emtpy if not needed. }
{ Called whenever the parameters were changed. Can be used to accelerate }
{ some calculations. See manual for more info }
label
10;
var
f2max, f2min, f2mean: extended; { extremal values of given data points }
interval: extended; { interval width, within which a smooth point is calculated }
eps: extended; { smallest distance between the x-values of two data points }
lowB, upB: extended; { boundaries of interval as x-values }
lowN, upN: integer; { boundaries of interval as index of data points }
xvals, yvals: ValsP; { arrays containing the given data points }
AA: extended; { exp. weighting factor }
xmean, xd: extended; { center of weighting }
ymean, yd: extended; { y vals }
nrInts: extended; { number of intervals }
nrPoints: integer; { number of points given }
nrGuide: integer; { number of points calculated for smooth curve }
regr: integer; { regression: 0=mean value, 1=linear, 2=quadratic }
xcol, ycol: integer; { the column numbers of the current data window }
i, j, k, count: integer; { counting variables }
log: boolean; { true if 'logarithmic' weighting is used }
numint: boolean; { true if interval depends on point density }
P: ptrToMyGlobals; { it is more comfortable to use P }
windID: longint; { window ID of data window to be used }
begin
{******************* prepare arrays ********************}
if (pb^.globals = nil) then
begin
StopExecution;
exit(First);
end;
P := ptrToMyGlobals(pb^.globals);
if (pb^.v[6] = 0) then
xcol := xColumn { generates an error if there is no data window }
else
xcol := round(pb^.v[6]);
if (pb^.v[7] = 0) then
ycol := yColumn
else
ycol := round(pb^.v[7]);
if (pb^.v[8] <> 0) then
begin
windID := round(pb^.v[8]);
SetCurrentWindow(windID);
end;
numint := (a[2] = 3);
regr := round(a[3]);
xvals := ValsP(NewPtr(sizeof(ValsT)));
yvals := ValsP(NewPtr(sizeof(ValsT)));
if (xvals = nil) | (yvals = nil) then
begin
if xvals <> nil then
DisposPtr(Ptr(xvals));
if yvals <> nil then
DisposPtr(Ptr(yvals));
if AlertBox('Not enough memory.') then
;
StopExecution;
exit(First);
end;
if TestStop then
exit(First);
{******************* get the points and sort if necessary ********************}
f2mean := 0;
nrPoints := 0;
if (xcol = 0) | (ycol = 0) then
begin
StopExecution;
exit(First);
end;
for i := 1 to NrRows do
if TestData(i, xcol) & TestData(i, ycol) then
begin
xd := GetData(i, xcol);
j := nrPoints + 1;
if numint & (nrPoints > 0) then { sorting, if intervals dependent on point density }
for j := nrPoints downto 1 do
if xd > xvals^[j] then
begin
for k := nrPoints downto j + 1 do
begin
xvals^[k + 1] := xvals^[k];
yvals^[k + 1] := yvals^[k];
end;
j := j + 1;
goto 10;
end; { for i }
10:
xvals^[j] := xd;
yvals^[j] := GetData(i, ycol);
nrPoints := nrPoints + 1;
if nrPoints = 1 then
begin
f2min := xd;
f2max := xd;
end;
if f2min > xd then
f2min := xd;
if f2min > 0 then
f2mean := f2mean + xd;
if f2max < xd then
f2max := xd;
end;
{******************* check wether enough points ********************}
if nrPoints < 1 then { at least 1 point }
begin
StopExecution;
if AlertBox('Not enough data for GuideLine.') then
;
P^.nrGuidePts := nrPoints;
StopExecution;
exit(first);
end;
if nrPoints <= 2 then { easy cases }
begin
P^.nrGuidePts := nrPoints;
exit(first);
end;
if TestStop then
exit(First);
{******************* prepare variables ********************}
nrInts := sqr((100 - a[1]) / 100) * (maxGuidePts - 1) + 1;
{ a[1] is limited between 0 and 100 }
{ nrInts will be between 1 and maxGuidePts }
nrGuide := 0;
eps := (f2max - f2min) / 2000; { minimum to define two x-values to be different }
log := false;
if numint then
nrInts := round(nrInts);
interval := (f2max - f2min) / nrInts;
lowB := f2min - interval / 2;
if not numint & (f2min > 0) then
begin
f2mean := f2mean / nrPoints;
{ logarithmic scale enforced or expected: }
log := (a[2] = 2) | ((a[2] = 0) & (f2mean < (f2max - f2min) / 3.6));
if log then
begin
interval := ln(f2max / f2min) / nrInts;
lowB := exp(ln(f2min) - interval / 2);
end;
end;
AA := 2 / interval;
{******************* calculate 1 point per interval ********************}
i := 1;
while (numint & (i <= round(nrInts))) | (not numint & (lowB < f2max)) do
begin
if numint then { calculate boundaries of interval }
begin
lowN := round((i - 1) / nrInts * nrPoints) + 1;
upN := round(i / nrInts * nrPoints);
end
else if log then
upB := exp(ln(lowB) + interval)
else
upB := lowB + interval;
xmean := 0; { sum up within interval }
ymean := 0;
count := 0;
if numint then
begin
for j := lowN to upN do
begin
xmean := xmean + xvals^[j];
ymean := ymean + yvals^[j];
count := count + 1;
end; { for j }
i := i + 1;
end
else
begin
for j := 1 to nrPoints do
begin
xd := xvals^[j];
if (xd >= lowB) & (xd < upB) then
begin
xmean := xmean + xd;
ymean := ymean + yvals^[j];
count := count + 1;
end;
end;
lowB := upB;
end;
if count = 0 then { calculate mean value }
cycle;
nrGuide := nrGuide + 1;
xmean := xmean / count;
ymean := ymean / count;
{**************** calculate weight-averaged value and derivative ******************}
if regr > 0 then { calculate point 1 }
FindAveragedVal(nrPoints, xvals, yvals, log, AA, regr, eps, xmean, ymean);
P^.xv[nrGuide] := xmean;
P^.yv[nrGuide] := ymean;
if TestStop then
exit(First);
if regr > 0 then { calculate point 2 }
begin
if log then
xd := exp(ln(xmean) + interval / 7) { delta x for slope ! (7 by accident) }
else
xd := xmean + interval / 7;
yd := ymean;
FindAveragedVal(nrPoints, xvals, yvals, log, AA, regr, eps, xd, yd);
P^.mv[nrGuide] := (yd - ymean) / (xd - xmean); { calculate the derivative }
end;
if TestStop then
exit(First);
end; { while (numint & i <= round(nrInts)) | (lowB < f2max) do }
{******************* clean up arrays********************}
P^.nrGuidePts := nrGuide;
DisposPtr(Ptr(xvals));
DisposPtr(Ptr(yvals));
{******************* calculate slope for regr=0********************}
if regr = 0 then
if nrGuide = 1 then
P^.mv[1] := 0
else if nrGuide = 2 then
begin
P^.mv[1] := (P^.yv[2] - P^.yv[1]) / (P^.xv[2] - P^.xv[1]);
P^.mv[2] := P^.mv[1];
end
else
begin
for i := 2 to nrGuide do
P^.mv[i] := (P^.yv[i] - P^.yv[i - 1]) / (P^.xv[i] - P^.xv[i - 1]);
P^.mv[1] := P^.mv[2];
for i := 2 to (nrGuide - 1) do
P^.mv[i] := (P^.mv[i + 1] + P^.mv[i]) / 2;
P^.mv[1] := 2 * P^.mv[1] - P^.mv[2];
P^.mv[nrGuide] := 2 * P^.mv[nrGuide] - P^.mv[nrGuide - 1];
end;
end; { First }
{***********************************************************************************************}
procedure Func (x: extended; { the x-value }
a: ParamArray; { the parameters }
var y: extended; { the y-value }
pb: ExtModulesParamBlockPtr); { the complete parameter block passed by pro Fit to the}
{ routines defined in this file. In most cases it can be ignored }
{ called to calculate the y-value of your function for a given x and a given }
{ set of parameters }
var
P: ptrToMyGlobals; { it is more comfortable to use P }
m, b: extended; { parameters for linear regression }
i, nrPoints: integer;
begin
if TestStop then
exit(Func);
if (pb^.globals = nil) then
begin
StopExecution;
exit(Func);
end;
P := ptrToMyGlobals(pb^.globals);
nrPoints := P^.nrGuidePts;
if nrPoints <= 1 then { for the easy cases ... }
begin
if nrPoints = 1 then
y := P^.yv[1]
else
y := 0;
exit(Func);
end;
for i := 1 to nrPoints do { find the two points, defining the curve piece needed }
if x < P^.xv[i] then
leave;
if i = 1 then { special case: curve left from the first point }
begin
m := P^.mv[1];
b := P^.yv[1] - m * P^.xv[1];
y := m * x + b;
end
else if i = (nrPoints + 1) then { special case: curve right to the last point }
begin
m := P^.mv[nrPoints];
b := P^.yv[nrPoints] - m * P^.xv[nrPoints];
y := m * x + b;
end
else
begin { intermediate points }
y := CubicCurve(x, P^.xv[i - 1], P^.yv[i - 1], P^.mv[i - 1], P^.xv[i], P^.yv[i], P^.mv[i])
end;
end; { func }
{***********************************************************************************************}
procedure Derivatives (x: extended; { the x-value }
a: ParamArray; { the parameters }
var dyda: ParamArray; { the derivatives }
pb: ExtModulesParamBlockPtr); { the complete parameter block passed by pro Fit to the }
{ routines defined in this file. In most cases it can be ignored }
{ Can be left empty if InitializeFunc sets hasDerivatives to false }
{ called to calculate the partial derivatives of the function with respect to }
{ its parameters. If you leave this function empty and set hasDerivatives to false in }
{ FuncInitialize, the derivatives will be calcuated numerically, otherwise pro Fit }
{ calls this function to obtain the values of ALL derivatives. }
{ As a result of the numerical calculation fitting will be slower }
begin
end; { Derivatives }
{***********************************************************************************************}
procedure Last (pb: ExtModulesParamBlockPtr);
{ Can be left emtpy if not needed. }
{ Called when calculating is through. See manual for more info }
begin
end;
{***********************************************************************************************}
procedure CleanUp (pb: ExtModulesParamBlockPtr);
{ called when the function or program is removed from pro Fit's menus }
{ in most cases, this function can be empty }
begin
end;
{***********************************************************************************************}
{ This is the main procedure through which all calls to the external module go. }
{ Main takes care of calling the right procedure with the right parameters depending on }
{ the value of "selector". }
{ You don't need to touch this procedure }
procedure mainMain (selector: integer; pb: ExtModulesParamBlockPtr);
begin
Startup(pb);
case selector of
kSetup:
begin
pb^.requiredGlobals := 0;
pb^.versionNumber := VERSIONNUMBER;
if sizeof(extended) = 10 then
pb^.codeType := CPU68noFPU
else if sizeof(extended) = 12 then
pb^.codeType := CPU68FPU
else
pb^.codeType := CPUPowerPC;
SetUp(pb^.moduleKind, pb^.name, pb^.requiredGlobals, pb);
end;
funcInitialize:
begin
pb^.hasDerivatives := false;
InitializeFunc(pb^.hasDerivatives, pb^.descr1, pb^.descr2, pb^.numberOfParams, pb^.a0, pb);
end;
funcCheck:
pb^.answer := ord(Check(pb^.paramNo, pb^.a0, pb));
funcFirst:
First(pb^.a^, pb);
funcFunc:
Func(pb^.x^, pb^.a^, pb^.y^, pb);
funcDerivatives:
Derivatives(pb^.x^, pb^.a^, pb^.dyda^, pb);
funcLast:
Last(pb);
kcleanup:
CleanUp(pb);
otherwise
end;
end;
end.